package com.yc.cloud.portal.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yc.cloud.api.common.MetaHumanRedisConstants;
import com.yc.cloud.api.enums.AnswerShowTypeEnums;
import com.yc.cloud.api.service.TTSCacheService;
import com.yc.cloud.basic.config.PathUtils;
import com.yc.cloud.basic.dto.BaseQueryParam;
import com.yc.cloud.basic.service.BaseCacheService;
import com.yc.cloud.basic.utils.MyFileConvertUtils;
import com.yc.cloud.basic.utils.SnowflakeIdGenerator;
import com.yc.cloud.entity.Answer;
import com.yc.cloud.entity.Company;
import com.yc.cloud.entity.Question;
import com.yc.cloud.mapper.AnswerMapper;
import com.yc.cloud.openai.constants.OpenAiConstants;
import com.yc.cloud.openai.dto.TTSResultDTO;
import com.yc.cloud.openai.enums.SupplierEnums;
import com.yc.cloud.openai.request.OpenAiParam;
import com.yc.cloud.openai.service.OpenAiService;
import com.yc.cloud.openai.service.OpenAiServiceFactory;
import com.yc.cloud.oss.service.OssFileService;
import com.yc.cloud.portal.dto.request.AnswerCacheVoiceParam;
import com.yc.cloud.portal.dto.request.AnswerQueryParam;
import com.yc.cloud.portal.dto.request.QuestionCreateParam;
import com.yc.cloud.portal.runnable.SaveAnswerRecordRunnable;
import com.yc.cloud.portal.service.*;
import com.yc.cloud.portal.vo.AnswerVo;
import com.yc.cloud.security.utils.MemberUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.BeanUtils;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Flux;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.apache.poi.ss.usermodel.*;

import static org.apache.poi.ss.usermodel.DataValidationConstraint.ValidationType.FORMULA;

/**
 * <p>
 * 答题库 服务实现类
 * </p>
 *
 * @author Yancy
 * @since 2024-01-31
 */
@Service
@Slf4j
public class AnswerServiceImpl extends ServiceImpl<AnswerMapper, Answer> implements AnswerService {

    private final OssFileService ossFileService;

    private final AnswerRecordService answerRecordService;

    private final MemberAccountService memberAccountService;

    private final CompanyService companyService;

    private final OpenAiServiceFactory openAiServiceFactory;

    private final TTSCacheService ttsCacheService;

    private final ThreadPoolExecutor threadPoolExecutor;

    private final SnowflakeIdGenerator snowflakeIdGenerator = new SnowflakeIdGenerator(1L);

    private final QuestionService questionService;
    private final int sampleRate16k = 16000;


    public AnswerServiceImpl(OssFileService ossFileService, AnswerRecordService answerRecordService, OpenAiServiceFactory openAiServiceFactory, CompanyService companyService, MemberAccountService memberAccountService, TTSCacheService ttsCacheService, QuestionService questionService) {
        this.ossFileService = ossFileService;
        this.answerRecordService = answerRecordService;
        this.openAiServiceFactory = openAiServiceFactory;
        this.memberAccountService = memberAccountService;
        this.companyService = companyService;
        this.ttsCacheService = ttsCacheService;
        this.questionService = questionService;
        //初始化线程池
        threadPoolExecutor = new ThreadPoolExecutor(5, 20, 20, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
    }

    @Override
    public IPage<Answer> list(BaseQueryParam queryParam) {
        val query = new QueryWrapper<Answer>();
        IPage<Answer> page;
        //排序
        if (StrUtil.isNotEmpty(queryParam.getSortBy())) {
            if (BaseQueryParam.ORDER_DESC.equals(queryParam.getOrder())) {
                query.orderByDesc(queryParam.getSortBy());
            } else if (BaseQueryParam.ORDER_ASC.equals(queryParam.getOrder())) {
                query.orderByAsc(queryParam.getSortBy());
            }
        }
        //分页
        if (queryParam.getPage()) {
            page = new Page<>(queryParam.getPageNum(), queryParam.getPageSize(), true);
        } else {
            page = new Page<>(queryParam.getPageNum(), baseMapper.selectCount(query), true);
        }
        return baseMapper.selectPage(page, query);
    }

    @Override
    public Answer create(Answer param) {
        val answer = new Answer();
        BeanUtils.copyProperties(param, answer);
        val saveFlag = save(answer);
        return saveFlag ? answer : null;
    }

    @Override
    public Answer get(Long id) {
        return getById(id);
    }

    @Override
    public boolean delete(Long id) {
        return removeById(id);
    }

    @Override
    public Map<Integer, List<AnswerVo>> query(AnswerQueryParam param) {
        val list = listByQuestionNo(param.getQuestionNo());
        if (CollUtil.isEmpty(list)) {
            return null;
        }
        //先对数据进行包装,例如如果是展示图片
        val result = list.stream().map(this::convertVo).collect(Collectors.toList());
        // 按照 step 字段进行分组，并且每个分组内对象按照 step 值进行升序排序
        return result.stream().collect(Collectors.groupingBy(AnswerVo::getStep, Collectors.mapping(obj -> obj, Collectors.toList())));
    }


    @Override
    public List<Answer> listByQuestionNo(String questionNo) {
        return baseMapper.selectList(new QueryWrapper<Answer>().eq(Answer.QUESTION_NO, questionNo));
    }


    @Override
    public Map<Integer, List<AnswerVo>> analysis(byte[] data, OpenAiParam param) {
        setAudioPath(data, param);
        return doAnalysis(param);
    }

    @Override
    public Flux<String> streamAnalysis(byte[] data, OpenAiParam param) {
        setAudioPath(data, param);
        setSupplierInfo(param);
        val supplier = param.getSupplier();
        val openAiService = openAiServiceFactory.getService(supplier);
//        param.setAppKey("sk-vSb8TxdeDBQXSx3i61BdEaAbDd234b6e99A0E5796f7892Af");
        return openAiService.analysisByStream(param);
    }

    @Override
    public Flux<ServerSentEvent<String>> streamAnalysis2(byte[] data, OpenAiParam param) {
        setAudioPath(data, param);
        setSupplierInfo(param);
        val supplier = param.getSupplier();
        param.setAppid("oScYj2iO");
        param.setAppKey("Link_MDL66qSUECziluJX0Qq77A7RUT2oqRjkDbfxaaQJGc");
        val openAiService = openAiServiceFactory.getService("LINKAI");
        return openAiService.analysisByStream2(param);
    }

    @Override
    public SseEmitter streamAnalysis3(byte[] data, OpenAiParam param) {
        setAudioPath(data, param);
        setSupplierInfo(param);
        val openAiService = openAiServiceFactory.getService("LINKAI");
        return openAiService.analysisByStream3(param);
    }


    @Override
    @SneakyThrows
    @Transactional
    public String importQuestionAndAnswer(MultipartFile file) {
//        val dstPath = PathUtils.getBasePath() + MemberUtils.getCurrentUserName() + File.separator + file.getOriginalFilename();
//        FileUtil.writeFromStream(file.getInputStream(), dstPath);
        Workbook workbook = new XSSFWorkbook(file.getInputStream());
        // 获取工作簿中的第一个工作表
        Sheet sheet = workbook.getSheetAt(0);
        // 创建一个列表来存储处理后的数据行
        List<List<String>> processedRows = new ArrayList<>();
        // 遍历工作表中的每一行
        for (Row row : sheet) {
            // 遍历行中的每个单元格
            List<String> rowData = new ArrayList<>();
            // 遍历行中的每个单元格
            val rowNumber = row.getRowNum();

            Question question = null;
            for (Cell cell : row) {
                //System.out.println(cell.getStringCellValue());
                // 根据单元格的类型获取值
                val columnIndex = cell.getColumnIndex();
                String content = "";
                switch (cell.getCellType()) {
                    case Cell.CELL_TYPE_STRING:
                        content = cell.getStringCellValue();
                        break;
                    case Cell.CELL_TYPE_NUMERIC:
                        content = String.valueOf(cell.getNumericCellValue());
                        break;
                    case Cell.CELL_TYPE_BOOLEAN:
                        content = String.valueOf(cell.getBooleanCellValue());
                        break;
                    case Cell.CELL_TYPE_FORMULA:
                        content = cell.getCellFormula();
                        break;
                    case Cell.CELL_TYPE_BLANK:
                    case Cell.CELL_TYPE_ERROR:
                        content = "";
                        break;
                    default:
                        break;
                }
                //问题
                if (columnIndex == 0 && rowNumber != 0) {
                    val questionCreateParam = new QuestionCreateParam();
                    questionCreateParam.setName(content);
                    question = questionService.create(questionCreateParam);
                }
                //答案
                else if (columnIndex == 1 && rowNumber != 0 && question != null && StrUtil.isNotEmpty(content)) {
                    val answer = new Answer();
                    answer.setQuestionNo(question.getQuestionNo());
                    answer.setContent(content);
                    answer.setShowType(AnswerShowTypeEnums.TEXT.getCode());
                    answer.setStep(1);
                    val saveFlag = save(answer);
                    log.info("content.length()={}", content.length());
                    if (saveFlag) {
                        val answerVo = new AnswerVo();
                        answerVo.setId(answer.getId());
                        val openAiService = openAiServiceFactory.getService(SupplierEnums.LINK_AI.getCode());
                        val openAiParam = new OpenAiParam();
                        openAiParam.setContent(content);
                        openAiParam.setGender(OpenAiConstants.GENDER_MAN);
                        openAiParam.setFormat(OpenAiConstants.AUDIO_FORMAT_WAV);
                        //需要生成男女两个语音文件
                        val ttsManResultDTO = getTTSResult(openAiService, openAiParam, answerVo);
                        openAiParam.setGender(OpenAiConstants.GENDER_WOMAN);
                        val ttsWomanResultDTO = getTTSResult(openAiService, openAiParam, answerVo);
                        if (content.length() > 50) {
                            content = question.getName() + "的答案,请通过编号:" + question.getQuestionNo() + "来查询";
                        }
                    }
                }
                rowData.add(content);
            }
            processedRows.add(rowData);
        }


        // 创建一个新的工作簿用于存放结果
        Workbook newWorkbook = new XSSFWorkbook();
        Sheet newSheet = newWorkbook.createSheet();

        // 将处理后的数据写入新的工作表
        for (int i = 0; i < processedRows.size(); i++) {
            // 创建新行，行号由当前行在列表中的索引 + 1 确定（因为列表索引从0开始）
            val row = processedRows.get(i);
            Row newRow = newSheet.createRow(i);
            for (int j = 0; j < row.size(); j++) {
                Cell newCell = newRow.createCell(j);
                newCell.setCellValue(row.get(j));
            }
        }

        // 保存为CSV文件
        String csvFileName = Objects.requireNonNull(file.getOriginalFilename()).replace(".xlsx", "_out.xlsx");
        val csvFilePath = PathUtils.getBasePath() + File.separator + MemberUtils.getCurrentUserName() + File.separator + csvFileName;
        File csvFile = new File(csvFilePath);
        try (FileOutputStream fileOut = new FileOutputStream(csvFile)) {
            newWorkbook.write(fileOut);
        }
        // 关闭工作簿
        workbook.close();
        newWorkbook.close();

        // 返回CSV文件的路径
        return PathUtils.convertNetUrl(csvFilePath);
    }

    @Override
    public boolean cacheVoiceContent(AnswerCacheVoiceParam param) {
        if (param.getUserId() != null) {
            val answeList = listByUserId(param.getUserId());
            if (CollUtil.isNotEmpty(answeList)) {
                answeList.forEach(answer -> {
                    if (answer.getShowType().equals(AnswerShowTypeEnums.TEXT.getCode())) {
                        val answerVo = new AnswerVo();
                        BeanUtils.copyProperties(answer, answerVo);
                        val openAiService = openAiServiceFactory.getService(SupplierEnums.LINK_AI.getCode());
                        val openAiParam = new OpenAiParam();
                        openAiParam.setContent(answer.getContent());
                        openAiParam.setGender(OpenAiConstants.GENDER_MAN);
                        openAiParam.setFormat(OpenAiConstants.AUDIO_FORMAT_WAV);
                        //需要生成男女两个语音文件
                        getTTSResult(openAiService, openAiParam, answerVo);
                        openAiParam.setGender(OpenAiConstants.GENDER_WOMAN);
                        getTTSResult(openAiService, openAiParam, answerVo);
                    }

                });
            }
        }
        return true;
    }


    private Map<Integer, List<AnswerVo>> doAnalysis(OpenAiParam param) {
        val doAnalysisStartTime = System.currentTimeMillis();
        val sessionId = snowflakeIdGenerator.generateId();
        //获取当前账号所属供应商
        setSupplierInfo(param);
        val supplier = param.getSupplier();
        val openAiService = openAiServiceFactory.getService(supplier);
        long analysisStartTime = System.currentTimeMillis();
        val openAiResultDto = openAiService.analysis(param);
        long analysisEndTime = System.currentTimeMillis();
        log.info("analysis spend times {}", (analysisEndTime - analysisStartTime));
        val nlpResult = openAiResultDto.getNlpContent();
        //从答案库中寻找是否存在配置的问题答案
        val answersList = listByQuestionNo(nlpResult);
        val answerVoList = new ArrayList<AnswerVo>();
        Map<Integer, List<AnswerVo>> result = new HashMap<>();
        if (CollUtil.isEmpty(answersList)) {
            val answerVo = new AnswerVo();
            BeanUtils.copyProperties(openAiResultDto, answerVo);
            answerVo.setSupplier(supplier);
            answerVo.setQuestionContent(openAiResultDto.getIatContent());
            answerVo.setUserId(MemberUtils.getCurrentUseId());
            answerVo.setSessionId(sessionId);
            answerVo.setStep(1);
            answerVo.setQuestionNo("-1");
            answerVo.setShowType(AnswerShowTypeEnums.TEXT.getCode());
            answerVo.setContent(nlpResult);
            answerVo.setNote(openAiResultDto.getTtsContent());
            //因为TCZLLD这个算法厂商是直接提供语音播报内容的,无需重复在获取tts合成,其他算法需要进行语音合成
            if (!SupplierEnums.TCZLLD.getCode().equals(supplier)) {
                param.setContent(nlpResult);
                val ttsResultDTO = getTTSResult(openAiService, param, answerVo);
                answerVo.setNote(ttsResultDTO.getResult());
                answerVo.setTtsCostTime(ttsResultDTO.getCostTime());
            }
            answerVoList.add(answerVo);
            result.put(1, answerVoList);
            threadPoolExecutor.execute(new SaveAnswerRecordRunnable(answerVo, answerRecordService));
        } else {
            final String finalSupplier = supplier;
            //转成vo对象
            val answersVoList = answersList.stream().map(this::convertVo).collect(Collectors.toList());
            //对答案进行数据包装
            result = answersVoList.stream().collect(Collectors.groupingBy(AnswerVo::getStep, Collectors.mapping(obj -> obj, Collectors.toList())));
            //获取第一步的答案,查看是否有文本信息,如果有则进行文本转语音文件
            result.forEach((key, topAnswerVoList) -> {
                for (AnswerVo answerVo : topAnswerVoList) {
                    if (AnswerShowTypeEnums.TEXT.getCode().equals(answerVo.getShowType())) {
                        BeanUtils.copyProperties(openAiResultDto, answerVo);
                        param.setContent(answerVo.getContent());
                        val ttsResultDTO = getTTSResult(openAiService, param, answerVo);
                        answerVo.setTtsCostTime(ttsResultDTO.getCostTime());
                        answerVo.setNote(ttsResultDTO.getResult());
                    }
                    answerVo.setSessionId(sessionId);
                    answerVo.setSupplier(finalSupplier);
                    answerVo.setQuestionContent(openAiResultDto.getIatContent());
                    answerVo.setUserId(MemberUtils.getCurrentUseId());
                    threadPoolExecutor.execute(new SaveAnswerRecordRunnable(answerVo, answerRecordService));
                }
            });

        }
        val doAnalysisEndTime = System.currentTimeMillis();
        log.info("doAnalysis spend times {}", (doAnalysisEndTime - doAnalysisStartTime));
        return result;
    }

    /**
     * 获取语音合成结果
     *
     * @param openAiService 需要调用的服务
     * @param param         调用参数
     * @param answerVo      回答内容,用于匹配redis key
     * @return 合成结果, base64格式
     */
    private TTSResultDTO getTTSResult(OpenAiService openAiService, OpenAiParam param, AnswerVo answerVo) {
        //没有匹配到答案库,标识答案是随机的,就不走缓存了
        if (answerVo.getId() == null) {
            return openAiService.getTTSResult(param);
        }
        var ttsResultDTO = new TTSResultDTO();
        //用来作为文本答案为语音播报的缓存key,区分男女
        String voice;
        String ttsKey;
        if (OpenAiConstants.GENDER_MAN.equals(param.getGender())) {
            ttsKey = MetaHumanRedisConstants.ANSWER_TSS_KEY_MAN.replace(MetaHumanRedisConstants.ANSWER_ID, String.valueOf(answerVo.getId()));
        } else {
            ttsKey = MetaHumanRedisConstants.ANSWER_TSS_KEY_FEMALE.replace(MetaHumanRedisConstants.ANSWER_ID, String.valueOf(answerVo.getId()));
        }
        if (ttsCacheService.existByKey(ttsKey)) {
            val startTime = System.currentTimeMillis();
            voice = ttsCacheService.getByKey(ttsKey);
            ttsResultDTO.setResult(voice);
            ttsResultDTO.setCostTime(0L);
            log.info("get tts result from cache, spend times {} ms", (System.currentTimeMillis() - startTime));
        } else {
            ttsResultDTO = openAiService.getTTSResult(param);
            ttsCacheService.setByKey(ttsKey, ttsResultDTO.getResult(), BaseCacheService.week_expire);
        }
        return ttsResultDTO;
    }

    @Override
    public Answer update(Long id, Answer param) {
        val answer = new Answer();
        BeanUtils.copyProperties(param, answer);
        answer.setId(id);
        val updateFlag = updateById(answer);
        return updateFlag ? answer : null;
    }


    @Override
    public AnswerVo convertVo(Answer answer) {
        if (answer == null) {
            return null;
        }
        val vo = new AnswerVo();
        BeanUtils.copyProperties(answer, vo);
        //不是文本则需要进行数据转化
        if (!AnswerShowTypeEnums.TEXT.getCode().equals(vo.getShowType())) {
            val newContent = ossFileService.getOssFileUrl(answer.getContent());
            vo.setContent(newContent);
        }
        return vo;
    }

    @Override
    public List<AnswerVo> convertVo(List<Answer> AnswerList) {
        if (CollUtil.isNotEmpty(AnswerList)) {
            return AnswerList.stream().map(this::convertVo).collect(Collectors.toList());
        }
        return new ArrayList<>();
    }

    private Company getCompany() {
        //先获取当前用户所属企业
        val memberAccount = memberAccountService.getById(MemberUtils.getCurrentUseId());
        if (memberAccount == null || memberAccount.getCompanyId() == null) {
            return null;
        }
        return companyService.get(memberAccount.getCompanyId());
    }

    private void setSupplierInfo(OpenAiParam param) {
        //获取当前账号所属供应商
        val company = getCompany();
        String supplier = "";
        //如果不属于任何企业或者企业中并没有明确指定算法厂商
        if (company == null || StrUtil.isEmpty(company.getBusinessNo())) {
            supplier = SupplierEnums.TCZLLD.getCode();
        } else {
            supplier = company.getBusinessNo();
            //设置算法厂商相关信息
            param.setAppid(company.getAppid());
            param.setAppKey(company.getAppKey());
            param.setTtsAppid(company.getTtsAppid());
            param.setTtsAppKey(company.getTtsAppKey());
            param.setIatAppid(company.getIatAppid());
            param.setIatAppKey(company.getIatAppKey());
        }
        param.setSupplier(supplier);
    }

    private void setAudioPath(byte[] data, OpenAiParam param) {
        val prefix = String.valueOf(System.currentTimeMillis());
        val fileName = prefix + "." + param.getFormat();
        var srcPath = Paths.get(PathUtils.getTempBasePath(), fileName).toString();
        FileUtil.writeBytes(data, srcPath);
        val fileName16K = fileName.replace(prefix, prefix + "_16k");
        val dstPath = Paths.get(PathUtils.getTempBasePath(), fileName16K).toString();
        //传入的是PCM格式,采样率不等于0,也不等于16K,需要做处理,转成16K的采样率
        if (param.getFormat().equals(OpenAiConstants.AUDIO_FORMAT_PCM)) {
            if (param.getSampleRate() != 0 && param.getSampleRate() != sampleRate16k) {
                MyFileConvertUtils.changePcmSampleRate(srcPath, dstPath, param.getSampleRate(), sampleRate16k);
                srcPath = dstPath;
            }
        } else if (param.getFormat().equals(OpenAiConstants.AUDIO_FORMAT_WAV)) {
            MyFileConvertUtils.changeWavSampleRate(srcPath, dstPath, sampleRate16k);
            srcPath = dstPath;
        }
        param.setPath(srcPath);
    }

    private List<Answer> listByUserId(Long userId) {
        return baseMapper.selectList(new QueryWrapper<Answer>().eq(Answer.CREATE_BY, userId));
    }

    public static void main(String[] args) {
        val srcPath = "/Users/jackgu/Downloads/yinxiao.wav";
        val dstPath = "/Users/jackgu/Downloads/yinxiao_16k.wav";
        MyFileConvertUtils.changeWavSampleRate(srcPath, dstPath, 16000);
    }
}
